package com.mathdroid.modules;

import java.util.LinkedList;

import javax.swing.SwingWorker;

import com.mathdroid.model.Model;
import com.mathdroid.model.abilities.Ability;
import com.mathdroid.model.simulation.CombatLog;
import com.mathdroid.model.simulation.SimulationStatistics;

/**
 * This abstract SwingWorker calls the simulation implementation of a 
 * ClassModule repeatedly.
 */
public abstract class Module 
extends SwingWorker<Void, Void> 
implements IModule {
	
	/**
	 * Generated UID.
	 */
	private static final long serialVersionUID = -5017342311895275207L;
	
	/**
	 * The maximum percent of progress.
	 */
	private static final double MAX_PERCENT = 100d;
	
	/**
	 * This simulator has to keep track of all abilities. This list contains
	 * the abilities from the abilities panel and "custom" abilities defined
	 * in the specialization module.
	 */
	private final LinkedList<Ability> abilities = new LinkedList<Ability>();
	
	/**
	 * The duration of the simulation in milliseconds.
	 */
	private double durationMs = 0d;
	
	/**
	 * The current time in the current simulation iteration.
	 */
	private double timeStampMs = 0d;
	
	/**
	 * The statistics for ALL simulations of this task.
	 */
	private SimulationStatistics statistics = new SimulationStatistics();
	
	/**
	 * The time to do nothing after an ability execution.
	 */
	private double idleTime = 0d;
	
	/**
	 * If this module should log the combat text.
	 */
	private boolean logging = false;
	
	/**
	 * The combat log.
	 */
	private CombatLog log = new CombatLog();
	
	/**
	 * The duration of the GCD.
	 */
	protected static final double GCD_DURATION_MS = 1500d;
	
	/**
	 * The time to sleep between two simulation iterations (in milliseconds).
	 */
//	private static final int LOOP_SLEEP_TIME_MS = 100;
	
	/**
	 * Number of iterations.
	 */
	private int iterations = 0;

	@Override
	protected final Void doInBackground() throws Exception {
		
		int percent = 0;
		int lastpercent = 0;
		int i = 1;
		this.durationMs = Model.getProfile().getEncounter().getDurationMs();
		
		this.before();
		
		while (i <= this.iterations && !this.isCancelled()) {
			
			/* Log the first iteration. After iterating over all simulations,
			 * there should be only one combat log. */
			if (i == 1) {
				this.setLogging(true);
			} else {
				this.setLogging(false);
			}
			
			this.cleanUp();
			this.init();
			this.simulate();
			//Thread.sleep(LOOP_SLEEP_TIME_MS);
			
			percent = (int) (i * MAX_PERCENT / this.iterations);
			
			if (percent != lastpercent) {
				this.setProgress(percent);
				lastpercent = percent;
			}
			
			i++;
		}
		
		/* Calculate the average values */
		this.statistics.calculateAverages(this.iterations);
		
		return null;
	}
	
	/**
	 * Set the number of simulation iterations.
	 * @param i Iterations.
	 */
	public final void setIterations(final int i) {
		this.iterations = i;
	}
	
	/**
	 * Initializes this simulation module. Resets and cleans data before doing
	 * a new simulation iteration.
	 */
	private void init() {
		this.timeStampMs = 0d;
		this.idleTime = 0d;
		
		for (Ability a : this.abilities) {
			a.setRemainingCooldownMs(0d);
		}
	}
	
	/**
	 * Call this method to increase time.
	 * @return True if the simulation should continue.
	 */
	protected final boolean iterateTime() {
		boolean running = true;
		
		this.timeStampMs += this.idleTime;
		
		if (this.timeStampMs >= this.durationMs) {
			running = false;
		}
		
		this.manageAbilitiesCoolDown();
		
		return running;
	}
	
	/**
	 * Reduce the CoolDown of the abilities, if there is any.
	 */
	private void manageAbilitiesCoolDown() {
		double remainingCooldown = 0d;
		
		for (Ability a : this.abilities) {
			remainingCooldown = a.getRemainingCooldownMs();
			if (remainingCooldown > 0) {
				if (remainingCooldown - this.idleTime > 0) {
					a.setRemainingCooldownMs(remainingCooldown - this.idleTime);
				} else {
					a.setRemainingCooldownMs(0d);
				}
			}
		}
	}
	
	/**
	 * Register an ability to the CoolDown management.
	 * @param a Ability.
	 */
	protected final void registerAbility(final Ability a) {
		a.setRemainingCooldownMs(0d);
		this.abilities.add(a);
	}
	
	/**
	 * Get a list of all managed abilities.
	 * @return Managed abilities.
	 */
	protected final LinkedList<Ability> getManagedAbilities() {
		return this.abilities;
	}

	/**
	 * Get the current timestamp in the simulation.
	 * @return Timestamp in milliseconds.
	 */
	protected final double whatTimeIsIt() {
		return this.timeStampMs;
	}
	
	/**
	 * Get the current single simulation statistics.
	 * @return Current statistics.
	 */
	protected final SimulationStatistics getStatistics() {
		return this.statistics;
	}
	
	/**
	 * Try to execute the ability. This method checks if you are allowed to 
	 * execute the ability and if so, returns what happened.
	 * @param a Ability to execute.
	 * @return Simulation Event.
	 */
	protected final SimulationEvent tryToExecute(final Ability a) {
		SimulationEvent e = null;
		
		if (this.isExecutable(a) && a.getRemainingCooldownMs() == 0) {
			
			a.setRemainingCooldownMs(a.getCooldownMs());
			e = this.execute(a);
			
		} else {
			e = new SimulationEvent(EExecutionResult.FAIL, 0d);
		}
		
		this.idleTime = e.getIdleTimeMs();
		
		return e;
	}
	
	/**
	 * Add text to the log.
	 * @param t Combat text.
	 */
	protected final void log(final String t) {
		if (this.logging) {
			this.log.addEntry(this.timeStampMs, t);
		}
	}
	
	/**
	 * Set if this simulation should log the combat text.
	 * @param b If this simulation has to log.
	 */
	public final void setLogging(final boolean b) {
		this.logging = b;
	}
	
	/**
	 * Get the combat log.
	 * @return Combat log.
	 */
	public final CombatLog getCombatLog() {
		return this.log;
	}
}
